/* TYLER HANN
 * TCH435 
 * 11081310 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>


#define PORT "9490" /* the port to connect to */
#define MYPORT "9491"
#define BUFSIZE 250 /* the size for the recv buffers */
#define MAXKEYS 100

int sendall(int new_fd, char *msg, int len, int flags)
{
	/*int send(int sockfd, const void *msg, int len, int flags); 
	send to respond to client
	use the new fd
	set flags to 0
	*/
	int bytes_sent = send(new_fd, msg, len, flags);

	if(bytes_sent < 0)
	{
		/* error on the send */
		fprintf(stderr, "errno: %d", errno);
		exit(1);
	}

	/* keep resending until everything makes it through */
	while(bytes_sent < len && bytes_sent >= 0)
	{
		/* not all of the bytes were sent, try sending rest */
		int temp_bytes = send(new_fd, &msg[bytes_sent],
			len-bytes_sent, flags);

		if(temp_bytes < 0)
		{
			/* error on the send */
			fprintf(stderr, "errno: %d", errno);
			exit(1);
		}
		else
			bytes_sent += temp_bytes;
	}

	return bytes_sent;
}


int sendalludp(int new_fd, char *msg, int len, int flags,
	const struct sockaddr *to)
{
	/*int send(int sockfd, const void *msg, int len, int flags); 
	send to respond to client
	use the new fd
	set flags to 0
	*/
	socklen_t tolen = sizeof(struct sockaddr_storage);
	int bytes_sent = sendto(new_fd, msg, len, flags, to, tolen);

	if(bytes_sent < 0)
	{
		/* error on the send */
		fprintf(stderr, "errno: %d", errno);
		exit(1);
	}

	/* keep resending until everything makes it through */
	while(bytes_sent < len && bytes_sent >= 0)
	{
		/* not all of the bytes were sent, try sending rest */
		int temp_bytes = sendto(new_fd, &msg[bytes_sent],
			len-bytes_sent, flags, to, tolen);

		if(temp_bytes < 0)
		{
			/* error on the send */
			fprintf(stderr, "errno: %d", errno);
			exit(1);
		}
		else
			bytes_sent += temp_bytes;
	}

	return bytes_sent;
}
			

int main(int argc, char *argv[])
{
	int status, client_sock, server_sock, s,
			bytes_sent, len, optval = 1; /* s is our socket fd*/
	struct addrinfo server_hints, client_hints, *server_info,
	*client_info, *p; /* servinfo will 
								point to the results */
	struct sockaddr_storage their_addr;
	socklen_t addr_size;

	fd_set read_fds, master;

	int maxfds;

	char recvBuf[BUFSIZE], sendvBuf[BUFSIZE];

	if (argc != 2) {
        fprintf(stderr,"usage: client hostname\n");
        exit(1);
    }

    /* set up hints while acting as client */
	memset(&client_hints, 0, sizeof client_hints);
    client_hints.ai_family = AF_UNSPEC;
    client_hints.ai_socktype = SOCK_DGRAM;

    /* set up hinits while acting as server */
	memset(&server_hints, 0, sizeof server_hints);
	server_hints.ai_family = AF_UNSPEC;
	server_hints.ai_socktype = SOCK_STREAM;
	server_hints.ai_flags = AI_PASSIVE;

	/* clear read_fds */
	FD_ZERO(&read_fds);

	/* set up client side proxy connection */

	if ((status = getaddrinfo(argv[1], PORT, &client_hints, &client_info)) != 0)
	{
	    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
	    exit(1);
	}

	// loop through all the results and connect to the first we can
    for(p = client_info; p != NULL; p = p->ai_next)
    {
        if ((client_sock = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1)
        {
			fprintf(stderr, "errno: %d", errno);
            exit(1);
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "failed to bind socket\n");
        exit(1);
    }



    /* client side proxy server set up, now set up server side */

	if ((status = getaddrinfo(NULL, MYPORT, &server_hints, &server_info)) != 0)
	{
	    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
	    exit(1);
	}

    s = socket(server_info->ai_family, server_info->ai_socktype,
					server_info->ai_protocol);

	//error checking
	if(s < 0)
	{
		fprintf(stderr, "errno: %d", errno);
		exit(1);
	}

	/* allow the socket address to be reused */
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
	/* if this fails, theoretically not a big deal, and I wouldn't
	know what to do anyways */

	/*int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
	*/
	if(bind(s, server_info->ai_addr, server_info->ai_addrlen) < 0)
	{
		fprintf(stderr, "errno: %d", errno);
		exit(1);
	}

	if(listen(s, 20) < 0)
	{
		fprintf(stderr, "errno: %d", errno);
		exit(1);
	}

	/* ready to communicate on socket descriptor s
	*/

	/* set addr_size to the size of the struct
	*/
	addr_size = sizeof(their_addr);
	if((server_sock = accept(s, (struct sockaddr *)&their_addr,
			&addr_size)) < 0)
	{
		fprintf(stderr, "errno: %d", errno);
		exit(1);
	}

	maxfds = server_sock;

	FD_SET(server_sock, &master);
	FD_SET(client_sock, &master);


	while(1) /*main accept loop - forward everything */
	{
		int q, send_bytes = 0, rec_bytes = 0;

		read_fds = master; /* refresh read_fds to be both server/client */

		/* use select to learn which sockets can be read from */
		if (select(maxfds+1, &read_fds, NULL, NULL, NULL) == -1)
		{
            perror("select");
            exit(4);
        }

        for(q=0; q <= maxfds; q++)
        {
        	if (FD_ISSET(q, &read_fds))
        	{
        		/* q will be the fd for a socket ready to be read */
        		if(q == server_sock)
        		{
        			/* check for message from client + forward to server */
					if((send_bytes = recv(server_sock, sendvBuf,
								BUFSIZE-1, 0)) < 0)
					{
						/*error*/
						fprintf(stderr, "errno: %d", errno);
						exit(1);
					}
					else
					{
						/* forward to server */
						sendvBuf[send_bytes] = '\0';

						len = strlen(sendvBuf);
						bytes_sent = sendalludp(client_sock, sendvBuf,
							len, 0, p->ai_addr);

						/* close the proxy if user quits/exits */
						if(strcmp(sendvBuf, "quit\n") == 0 ||
							strcmp(sendvBuf, "exit\n") == 0)
						{
							exit(0);
						}
					}
        		}
        		else if(q == client_sock)
        		{
					/* check for message from server + forward to client */
					if((rec_bytes = recvfrom(client_sock, recvBuf,
						BUFSIZE-1, 0, (struct sockaddr *)&their_addr,
						&addr_size)) < 0)
					{
						/*error*/
						fprintf(stderr, "errno: %d", errno);
						exit(1);
					}
					else
					{
						/* forward to client */
						recvBuf[rec_bytes] = '\0';

						len = strlen(recvBuf);
						bytes_sent = sendall(server_sock, recvBuf, len, 0);
					}
        		}
        	}
        }
	}

	close(s);
	close(server_sock);
	close(client_sock);
	freeaddrinfo(server_info); // free the linked-list
	freeaddrinfo(client_info);

	return 0;
}